package cn.fzc.example.redis.distributedlock;

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @Author: fu.zc
 * @Date: 2020/8/5 09:41
 * @Description:
 */
public class LuaDistributeLock implements ILock {

    /**
     * 加锁脚本
     */
    private DefaultRedisScript<Long> lockScript;
    /**
     * 解锁脚本
     */
    private DefaultRedisScript<Long> unlockScript;

    private final StringRedisTemplate stringRedisTemplate;

    private ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return UUID.randomUUID().toString();
        }
    };


    public LuaDistributeLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        init();
    }

    /**
     * 加在lua语句
     */
    private void init() {
        // Lock script
        lockScript = new DefaultRedisScript<Long>();
        lockScript.setScriptSource(
                new ResourceScriptSource(new ClassPathResource("cn/fzc/example/redis/distributedlock/script/lock.lua")));
        lockScript.setResultType(Long.class);
        // unlock script
        unlockScript = new DefaultRedisScript<Long>();
        unlockScript.setScriptSource(
                new ResourceScriptSource(new ClassPathResource("cn/fzc/example/redis/distributedlock/script/unlock.lua")));
        unlockScript.setResultType(Long.class);
    }


    @Override
    public void lock(String lockKey) {
        Assert.notNull(lockKey, "lockKey can`t ben null");
        while (true) {
            List<String> keyList = new ArrayList<>();
            keyList.add(lockKey);
            keyList.add(threadLocal.get());

            if (stringRedisTemplate.execute(lockScript, keyList, String.valueOf(LOCK_MAX_EXIST_TIME * 1000)) > 0) {
                break;
            } else {
                try {
                    Thread.sleep(10, (int)(Math.random() * 500));
                } catch (InterruptedException e) {
                    break;
                }
            }
        }

    }

    @Override
    public void unlock(String lockKey) {
        List<String> keyList = new ArrayList<>();
        keyList.add(lockKey);
        keyList.add(threadLocal.get());
        stringRedisTemplate.execute(unlockScript, keyList);
    }
}
